setfenv(1, require'winapi')
require'winapi.window'
require'winapi.comctl'
require'winapi.router'

--creation

WC_LISTBOX             = 'ListBox'

LBS_NOTIFY             = 0x0001
LBS_SORT               = 0x0002
LBS_NOREDRAW           = 0x0004
LBS_MULTIPLESEL        = 0x0008
LBS_OWNERDRAWFIXED     = 0x0010
LBS_OWNERDRAWVARIABLE  = 0x0020
LBS_HASSTRINGS         = 0x0040
LBS_USETABSTOPS        = 0x0080
LBS_NOINTEGRALHEIGHT   = 0x0100
LBS_MULTICOLUMN        = 0x0200
LBS_WANTKEYBOARDINPUT  = 0x0400
LBS_EXTENDEDSEL        = 0x0800
LBS_DISABLENOSCROLL    = 0x1000
LBS_NODATA             = 0x2000
LBS_NOSEL              = 0x4000
LBS_COMBOBOX           = 0x8000
LBS_STANDARD           = bit.bor(LBS_NOTIFY, LBS_SORT, WS_VSCROLL, WS_BORDER)

LISTBOX_DEFAULTS = {
	class = WC_LISTBOX,
	style = bit.bor(WS_CHILD, WS_VISIBLE, LBS_STANDARD),
	style_ex = bit.bor(WS_EX_CLIENTEDGE),
	x = 10, y = 10,
	w = 200, h = 100,
}

function CreateListBox(info)
	info = update({}, LISTBOX_DEFAULTS, info)
	return CreateWindow(info)
end

--commands

LB_ADDSTRING             = 0x0180
LB_INSERTSTRING          = 0x0181
LB_DELETESTRING          = 0x0182
LB_SELITEMRANGEEX        = 0x0183
LB_RESETCONTENT          = 0x0184
LB_SETSEL                = 0x0185
LB_SETCURSEL             = 0x0186
LB_GETSEL                = 0x0187
LB_GETCURSEL             = 0x0188
LB_GETTEXT               = 0x0189
LB_GETTEXTLEN            = 0x018A
LB_GETCOUNT              = 0x018B
LB_DIR                   = 0x018D
LB_GETTOPINDEX           = 0x018E
LB_FINDSTRING            = 0x018F
LB_GETSELCOUNT           = 0x0190
LB_GETSELITEMS           = 0x0191
LB_SETTABSTOPS           = 0x0192
LB_GETHORIZONTALEXTENT   = 0x0193
LB_SETHORIZONTALEXTENT   = 0x0194
LB_SETCOLUMNWIDTH        = 0x0195
LB_ADDFILE               = 0x0196
LB_SETTOPINDEX           = 0x0197
LB_GETITEMRECT           = 0x0198
LB_GETITEMDATA           = 0x0199
LB_SETITEMDATA           = 0x019A
LB_SELITEMRANGE          = 0x019B
LB_SETANCHORINDEX        = 0x019C
LB_GETANCHORINDEX        = 0x019D
LB_SETCARETINDEX         = 0x019E
LB_GETCARETINDEX         = 0x019F
LB_SETITEMHEIGHT         = 0x01A0
LB_GETITEMHEIGHT         = 0x01A1
LB_FINDSTRINGEXACT       = 0x01A2
LB_SETLOCALE             = 0x01A5
LB_GETLOCALE             = 0x01A6
LB_SETCOUNT              = 0x01A7
LB_INITSTORAGE           = 0x01A8
LB_ITEMFROMPOINT         = 0x01A9
LB_MULTIPLEADDSTRING     = 0x01B1
LB_GETLISTBOXINFO        = 0x01B2


ListBox_Enable = EnableWindow

function ListBox_GetCount(hwndCtl)
	return SNDMSG(hwndCtl, LB_GETCOUNT)
end

function ListBox_ResetContent(hwndCtl)
	checknz(SNDMSG(hwndCtl, LB_RESETCONTENT))
end

function ListBox_InsertString(hwndCtl, index, lpsz)
	if type(lpsz) == 'nil' then index, lpsz = nil, index end
	if index then
		return checkpoz(SNDMSG(hwndCtl, LB_INSERTSTRING, countfrom0(index), ffi.cast('LPCTSTR', wcs(lpsz))))+1
	else
		return checkpoz(SNDMSG(hwndCtl, LB_ADDSTRING, 0, ffi.cast('LPCTSTR', wcs(lpsz))))
	end
end

function ListBox_InsertItemData(hwndCtl, index, data)
	if type(data) == 'nil' then index, data = nil, index end
	if index then
		return checkpoz(SNDMSG(hwndCtl, LB_INSERTSTRING, countfrom0(index), data))+1
	else
		return checkpoz(SNDMSG(hwndCtl, LB_ADDSTRING, 0, data))+1
	end
end

function ListBox_DeleteString(hwndCtl, index)
	return checkpoz(SNDMSG(hwndCtl, LB_DELETESTRING, countfrom0(index)))
end

function ListBox_GetText(hwndCtl, index)
	local buf = WCS(checkpoz(SNDMSG(hwndCtl, LB_GETTEXTLEN, countfrom0(index))))
	checkpoz(SNDMSG(hwndCtl, LB_GETTEXT, countfrom0(index), buf))
	return buf
end

function ListBox_GetItemData(hwndCtl, index)
	return SNDMSG(hwndCtl, LB_GETITEMDATA, countfrom0(index))
end

function ListBox_SetItemData(hwndCtl, index, data)
	checkpoz(SNDMSG(hwndCtl, LB_SETITEMDATA, countfrom0(index), data))
end

function ListBox_FindString(hwndCtl, lpszFind, indexStart)
	return checkpoz(SNDMSG(hwndCtl, LB_FINDSTRING, countfrom0(indexStart or -1), ffi.cast('LPCTSTR', wcs(lpszFind))))+1
end

function ListBox_FindItemData(hwndCtl, data, indexStart)
	return checkpoz(SNDMSG(hwndCtl, LB_FINDSTRING, countfrom0(indexStart or -1), data))+1
end

function ListBox_FindStringExact(hwndCtl, lpszFind, indexStart)
	return checkpoz(SNDMSG(hwndCtl, LB_FINDSTRINGEXACT, countfrom0(indexStart or -1), ffi.cast('LPCTSTR', wcs(lpszFind))))+1
end

function ListBox_SetSel(hwndCtl, fSelect, index)
	checkpoz(SNDMSG(hwndCtl, LB_SETSEL, fSelect, countfrom0(index)))
end

function ListBox_GetSel(hwndCtl, index)
	return checkpoz(SNDMSG(hwndCtl, LB_GETSEL, countfrom0(index))) ~= 0
end

function ListBox_SelItemRange(hwndCtl, fSelect, first, last)
   checkpoz(SNDMSG(hwndCtl, LB_SELITEMRANGE, fSelect, MAKELPARAM(countfrom0(first), countfrom0(last))))
end

function ListBox_GetCurSel(hwndCtl)
	return checkpoz(SNDMSG(hwndCtl, LB_GETCURSEL))+1
end

function ListBox_SetCurSel(hwndCtl, index)
	local check = index == -1 and pass or checkpoz
	check(SNDMSG(hwndCtl, LB_SETCURSEL, countfrom0(index)))
end

function ListBox_GetSelCount(hwndCtl)
	return checkpoz(SNDMSG(hwndCtl, LB_GETSELCOUNT))
end

function ListBox_GetSelItems(hwndCtl)
	local n = GetSelCount(hwndCtl)
	local items = ffi.new('int[?]', n)
	n = checkpoz(SNDMSG(hwndCtl, LB_GETSELITEMS, n, items))
	--TODO: should we return the zero-based cdata array and its length instead?
	local t={}
	for i=1,n do
		t[i] = items[i-1]+1
	end
	return t
end

function ListBox_GetTopIndex(hwndCtl)
	return checkpoz(SNDMSG(hwndCtl, LB_GETTOPINDEX))+1
end

function ListBox_SetTopIndex(hwndCtl, indexTop)
	checkpoz(SNDMSG(hwndCtl, LB_SETTOPINDEX, countfrom0(indexTop)))
end

function ListBox_SetColumnWidth(hwndCtl, cxColumn)
	SNDMSG(hwndCtl, LB_SETCOLUMNWIDTH, cxColumn)
end

function ListBox_SetTabStops(hwndCtl, tabs)
	local tabs, n = array('int', tabs)
	checknz(SNDMSG(hwndCtl, LB_SETTABSTOPS, n, tabs))
end

function ListBox_GetHorizontalExtent(hwndCtl)
	SNDMSG(hwndCtl, LB_GETHORIZONTALEXTENT)
end

function ListBox_SetHorizontalExtent(hwndCtl, cxExtent)
	SNDMSG(hwndCtl, LB_SETHORIZONTALEXTENT, cxExtent)
end

function ListBox_GetItemRect(hwndCtl, index, rect)
	local rect = rect or ffi.new'RECT'
	checkpoz(SNDMSG(hwndCtl, LB_GETITEMRECT, countfrom0(index), ffi.cast('RECT *', rect)))
	return rect
end

function ListBox_SetCaretIndex(hwndCtl, index)
	checkz(SNDMSG(hwndCtl, LB_SETCARETINDEX, countfrom0(index)))
end

function ListBox_GetCaretIndex(hwndCtl)
	return checkpoz(SNDMSG(hwndCtl, LB_GETCARETINDEX))+1
end

function ListBox_SetItemHeight(hwndCtl, index, cy)
   SNDMSG(hwndCtl, LB_SETITEMHEIGHT, countfrom0(index), MAKELPARAM(cy, 0))
end

function ListBox_GetItemHeight(hwndCtl, index)
	return checkpoz(SNDMSG(hwndCtl, LB_GETITEMHEIGHT, countfrom0(index)))
end

function ListBox_Dir(hwndCtl, attrs, lpszFileSpec)
	return checkpoz(SNDMSG(hwndCtl, LB_DIR, attrs, ffi.cast('LPCTSTR', wcs(lpszFileSpec))))
end

--notifications

LBN_ERRSPACE         = -2
LBN_SELCHANGE        = 1
LBN_DBLCLK           = 2
LBN_SELCANCEL        = 3
LBN_SETFOCUS         = 4
LBN_KILLFOCUS        = 5

--showcase

if not ... then
require'winapi.showcase'
local window, router = ShowcaseWindow()
local lb1 = CreateListBox{parent = window, style = bit.bor(LISTBOX_DEFAULTS.style, LBS_MULTIPLESEL)}
local lb2 = CreateListBox{parent = window, x = 220}
for i=1,100 do
	ListBox_InsertString(lb1, 'Item %03d' % i)
end
router:set_handler('Control', lb1, function(WM, LBN)
	if WM == WM_COMMAND then
		ListBox_InsertString(lb2, findname('LBN_', LBN))
	end
end)
MessageLoop()
end

